package integrity

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"hash"
	"io"
	"os"
	"time"

	"github.com/zeebo/blake3"
)

// HashCalculator provides hash calculation functionality
type HashCalculator struct {
	config *IntegrityConfig
}

// NewHashCalculator creates a new hash calculator
func NewHashCalculator(config *IntegrityConfig) *HashCalculator {
	if config == nil {
		config = &IntegrityConfig{
			DefaultAlgorithm: SHA256,
			EnableChecksum:   false,
		}
	}
	return &HashCalculator{
		config: config,
	}
}

// CalculateHash calculates hash for given data using specified algorithm
func (h *HashCalculator) CalculateHash(ctx context.Context, data []byte, algorithm HashAlgorithm) (string, error) {
	if len(data) == 0 {
		return "", fmt.Errorf("empty data provided")
	}

	hasher, err := h.createHasher(algorithm)
	if err != nil {
		return "", fmt.Errorf("failed to create hasher: %w", err)
	}

	// Write data to hasher
	if _, err := hasher.Write(data); err != nil {
		return "", fmt.Errorf("failed to write data to hasher: %w", err)
	}

	// Get hash sum
	hashSum := hasher.Sum(nil)
	return hex.EncodeToString(hashSum), nil
}

// CalculateFileHash calculates hash for a file
func (h *HashCalculator) CalculateFileHash(ctx context.Context, filePath string, algorithm HashAlgorithm) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	hasher, err := h.createHasher(algorithm)
	if err != nil {
		return "", fmt.Errorf("failed to create hasher: %w", err)
	}

	// Copy file content to hasher
	if _, err := io.Copy(hasher, file); err != nil {
		return "", fmt.Errorf("failed to read file: %w", err)
	}

	// Get hash sum
	hashSum := hasher.Sum(nil)
	return hex.EncodeToString(hashSum), nil
}

// VerifyHash verifies data against expected hash
func (h *HashCalculator) VerifyHash(ctx context.Context, data []byte, expectedHash string, algorithm HashAlgorithm) error {
	calculatedHash, err := h.CalculateHash(ctx, data, algorithm)
	if err != nil {
		return fmt.Errorf("failed to calculate hash: %w", err)
	}

	if calculatedHash != expectedHash {
		return &IntegrityError{
			Type:         ErrHashMismatch,
			ExpectedHash: expectedHash,
			ActualHash:   calculatedHash,
			Algorithm:    algorithm,
		}
	}

	return nil
}

// VerifyFileHash verifies file against expected hash
func (h *HashCalculator) VerifyFileHash(ctx context.Context, filePath string, expectedHash string, algorithm HashAlgorithm) error {
	calculatedHash, err := h.CalculateFileHash(ctx, filePath, algorithm)
	if err != nil {
		return fmt.Errorf("failed to calculate file hash: %w", err)
	}

	if calculatedHash != expectedHash {
		return &IntegrityError{
			Type:         ErrHashMismatch,
			ExpectedHash: expectedHash,
			ActualHash:   calculatedHash,
			Algorithm:    algorithm,
		}
	}

	return nil
}

// createHasher creates a new hasher based on algorithm
func (h *HashCalculator) createHasher(algorithm HashAlgorithm) (hash.Hash, error) {
	switch algorithm {
	case SHA256:
		return sha256.New(), nil
	case BLAKE3:
		return blake3.New(), nil
	default:
		return nil, fmt.Errorf("unsupported hash algorithm: %s", algorithm)
	}
}

// SimpleChecksumCalculator provides simple checksum calculation
type SimpleChecksumCalculator struct{}

// NewSimpleChecksumCalculator creates a new simple checksum calculator
func NewSimpleChecksumCalculator() *SimpleChecksumCalculator {
	return &SimpleChecksumCalculator{}
}

// CalculateChecksum calculates a simple checksum for data
func (s *SimpleChecksumCalculator) CalculateChecksum(data []byte) []byte {
	if len(data) == 0 {
		return []byte{}
	}

	// Simple XOR checksum
	checksum := byte(0)
	for _, b := range data {
		checksum ^= b
	}
	return []byte{checksum}
}

// VerifyChecksum verifies data against expected checksum
func (s *SimpleChecksumCalculator) VerifyChecksum(data []byte, expectedChecksum []byte) error {
	if len(expectedChecksum) == 0 {
		return fmt.Errorf("empty expected checksum")
	}

	calculatedChecksum := s.CalculateChecksum(data)

	if len(calculatedChecksum) != len(expectedChecksum) {
		return &IntegrityError{
			Type:           ErrChecksumMismatch,
			ExpectedChecksum: expectedChecksum,
			ActualChecksum:   calculatedChecksum,
		}
	}

	for i := range calculatedChecksum {
		if calculatedChecksum[i] != expectedChecksum[i] {
			return &IntegrityError{
				Type:           ErrChecksumMismatch,
				ExpectedChecksum: expectedChecksum,
				ActualChecksum:   calculatedChecksum,
			}
		}
	}

	return nil
}

// IntegrityError represents an integrity verification error
type IntegrityError struct {
	Type             ErrorType
	ExpectedHash     string
	ActualHash       string
	ExpectedChecksum []byte
	ActualChecksum   []byte
	Algorithm        HashAlgorithm
	Message          string
}

// ErrorType represents the type of integrity error
type ErrorType string

const (
	// ErrHashMismatch indicates hash mismatch
	ErrHashMismatch ErrorType = "hash_mismatch"
	// ErrChecksumMismatch indicates checksum mismatch
	ErrChecksumMismatch ErrorType = "checksum_mismatch"
	// ErrDataCorrupted indicates data corruption
	ErrDataCorrupted ErrorType = "data_corrupted"
)

// Error returns the error message
func (e *IntegrityError) Error() string {
	switch e.Type {
	case ErrHashMismatch:
		return fmt.Sprintf("hash mismatch: expected %s, got %s (algorithm: %s)",
			e.ExpectedHash, e.ActualHash, e.Algorithm)
	case ErrChecksumMismatch:
		return fmt.Sprintf("checksum mismatch: expected %v, got %v",
			e.ExpectedChecksum, e.ActualChecksum)
	case ErrDataCorrupted:
		if e.Message != "" {
			return fmt.Sprintf("data corrupted: %s", e.Message)
		}
		return "data corrupted"
	default:
		return "unknown integrity error"
	}
}

// Utility functions for integrity checking

// GenerateIntegrityMetadata generates complete integrity metadata for data
func GenerateIntegrityMetadata(data []byte, algorithm HashAlgorithm, enableChecksum bool) (*IntegrityMetadata, error) {
	if len(data) == 0 {
		return nil, fmt.Errorf("empty data provided")
	}

	metadata := &IntegrityMetadata{
		Algorithm: algorithm,
		Size:      int64(len(data)),
		CreatedAt: time.Now(),
		Status:    StatusUnknown,
	}

	// Calculate hash
	calculator := NewHashCalculator(nil)
	hash, err := calculator.CalculateHash(context.Background(), data, algorithm)
	if err != nil {
		return nil, fmt.Errorf("failed to calculate hash: %w", err)
	}
	metadata.Hash = hash

	// Calculate checksum if enabled
	if enableChecksum {
		checksumCalc := NewSimpleChecksumCalculator()
		metadata.Checksum = checksumCalc.CalculateChecksum(data)
	}

	return metadata, nil
}

// QuickVerify performs quick verification using both hash and checksum
func QuickVerify(data []byte, metadata *IntegrityMetadata) error {
	if metadata == nil {
		return fmt.Errorf("no integrity metadata provided")
	}

	// Verify hash
	calculator := NewHashCalculator(nil)
	if err := calculator.VerifyHash(context.Background(), data, metadata.Hash, metadata.Algorithm); err != nil {
		return err
	}

	// Verify checksum if available
	if len(metadata.Checksum) > 0 {
		checksumCalc := NewSimpleChecksumCalculator()
		if err := checksumCalc.VerifyChecksum(data, metadata.Checksum); err != nil {
			return err
		}
	}

	return nil
}